


#include <string>
using std::string;

#include <climits>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <cstring>

#include "httputil.h"
#include "HTTPServer.h"

const string HTTPServer::SERVER ("SimpleSever/0.01");

HTTPServer::HTTPServer():
    m_status(ServerClosed),
    m_conf(),
    m_port(0),
    m_log(NULL),
    m_sockfd(-1)
{

}

void HTTPServer::log(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    if (m_log) {
        fprintf(m_log, "[HTTPServer] ");
        vfprintf(m_log, fmt, ap);
    }
    va_end(ap);
}

bool HTTPServer::loadConf(const string& confFilePath)
{
    int conffd = open(confFilePath.c_str(), O_RDONLY);
    if (conffd == -1) {
        log("Failed to open configure file\n");
        return false;
    }
    if (!m_conf.parseHeader(conffd)) {
        log("Failed to load configuration\n");
        return false;
    }
    return true;
}

HTTPServer::Status HTTPServer::getCurrentStatus() const {
    return m_status;
}

bool HTTPServer::listen()
{
    if (m_sockfd == -1) {
        log("listen: has not craeted socket yet\n");
        return false;
    }
    while (true) {
        struct sockaddr_in cli_addr;
        socklen_t clilen = sizeof(cli_addr);
        int newsockfd = accept(m_sockfd,
                               (struct sockaddr *) &cli_addr,
                                   &clilen);

        string initLine;
        bool eof;
        HTTPUtil::readline(initLine, &eof, newsockfd);
        log("readline: %s\n", initLine.c_str());
        HTTPUtil::HTTPMethod method;
        string path, version;
        MessageHeader headers;
        int length = 0;string content;
        bool success;
        if (!HTTPUtil::parseRequestLine(method, path, version, initLine))
            goto BadRequest;
        if (method == HTTPUtil::nMethod)
            goto BadRequest;

        if (!headers.parseHeader(newsockfd)) {
            log("Failed to parse headers\n");
            goto BadRequest;

        }
        if (headers.isFieldExists("Content-Length"))
            length = atoi(headers.getFieldValue("Content-Length").c_str());
        if (!HTTPUtil::readBytes(content, newsockfd, length)) {
            log("Failed to read content\n");
            goto BadRequest;
        }

        success = writeResponse(method, headers, path, newsockfd);
        goto CloseConnect;
        continue;
    BadRequest:
    CloseConnect:
        ::close(newsockfd);
        continue;
    }
}

bool HTTPServer::setup(int port) {
    m_port = port;
    struct sockaddr_in serv_addr, cli_addr;
    m_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (m_sockfd < 0) {
        log("Failed to create socket\n");
        return false;
    }
    memset((char *) &serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(m_port);
    if (bind(m_sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0) {
        log("ERROR on binding\n");
        return false;
    }
    if (::listen(m_sockfd, 0) != 0) {
        log("ERROR on listen\n");
        return false;
    }
    return true;
}

void HTTPServer::close()
{
    ::close(m_sockfd);
}

void HTTPServer::setLog(FILE* file)
{
    m_log = file;
}

bool HTTPServer::writeResponse(HTTPUtil::HTTPMethod method, const MessageHeader& headers, const string& path, int sock) {
        // TODO: handle host field
        string filePath = m_conf.getFieldValue("staticPath") + "/" + path;
        int fd = open(filePath.c_str(), O_RDONLY);
        if (fd == -1)
            return notfound(sock);
        struct stat fileAttr;
        if (fstat(fd, &fileAttr) == -1) {
            log("fstat failed");
            return false;
        }
        string response;
        response.append("HTTP/1.1 200 OK\r\n");
        response.append("SERVER: " + SERVER + "\r\n");
        response.append("Date: " + getGMT()+"\r\n");
        response.append("Connection: close\r\n");
        response.append("Content-Type: " + getmime(filePath) + "\r\n");
        bool success = HTTPUtil::writeBytes(response.data(), sock,
                                            response.length());
        if (!success) return false;
        return writeContent(fd, sock, fileAttr.st_size);

}

bool HTTPServer::writeContent(int fd, int sock, int length)
{

    string response;
    char tempbuf[32];
    snprintf(tempbuf, 32, "%d", length);
    response.append("Content-Length: " + string(tempbuf)+ "\r\n");
    response.append("\r\n");
    bool success = HTTPUtil::writeBytes(response.data(), sock,
                                        response.length());
    if (!success) return false;

    return HTTPUtil::copy(fd, sock, length);
}

bool HTTPServer::writeContent(const string& content, int sock)
{
    string response;
    char tempbuf[32];
    snprintf(tempbuf, 32, "%d", content.length());;
    response.append("Content-Length: " + string(tempbuf)+ "\r\n");
    response.append("\r\n");
    bool success = HTTPUtil::writeBytes(response.data(), sock,
                                        response.length());
    if (!success) return false;

    return HTTPUtil::writeBytes(content.data(), sock, content.length());
}

bool HTTPServer::notfound(int sock) {
    string response("HTTP/1.1 404 Not Found\r\n");
    return HTTPUtil::writeBytes(response.data(), sock, response.length());
}

string HTTPServer::getGMT() const {
    time_t rawtime;
    time(&rawtime);
    struct tm* timeinfo = gmtime(&rawtime);
    char * str = asctime(timeinfo);
    int len = strlen(str);
    return string(str, len - 1); // erase the last \n
}

string HTTPServer::getmime(const string& filePath) const {
    size_t pos = filePath.find_last_of('.');
    if(pos == string::npos)
        return string("text/plain"); //default
    string extention = filePath.substr(pos);
    if (m_conf.isFieldExists("mime"+extention))
        return m_conf.getFieldValue("mime"+extention);
    else
        return string("text/plain");
}
